从原型到原型链
构造函数创建对象
1 | function Person() { |
在这个例子中, Person 就是一个构造函数, person 是构造函数的实例
prototype
每个函数都有一个 prototype 属性
1 | function Person() { |
函数的 prototype 属性指向了一个对象, 这个对象是调用该构造函数而创建的实例的原型,是例子中 person1、person2的原型
原型可以这样理解: 每一个 JavaScript 对象 (null 除外) 在创建的时候就会与另一个对象产生关联,这就是原型,每一个对象都会从原型’继承’属性
__proto__
每个对象都有一个隐藏的属性—‘__proto__
‘,这个属性指向了创建这个对象的函数的 prototype。 即:
person.__proto__ = Person.prototype
, 这个属性也被称为隐式原型
constructor
每个原型都有一个 constructor 属性指向关联的构造函数,为什么没有指向实例的属性呢,因为实例可以有千千万万个。
1 | function Person () {} |
关系图更新如上
1 | function Person() { |
实例与原型
当读取实例属性时,会先从实例开始查找,如果查找不到,会向上往实例的原型中的属性找到,还没有,就去原型的原型中去找,一直到最顶层为止
1 | function Person() { |
原型的原型
原型也是一个对象,那么它就可以通过最原始的方法被创建
1 | var obj = new Object(); |
所以原型对象是通过 Object 构造函数生成的,实例的 proto 属性指向构造函数的 prototype属性,更新关系图
原型链
那么 Object.prototype 的原型呢?
1 | console.log(Object.prototype.__proto__ === null) // true |
null究竟代表了什么呢?
引用阮一峰老师的 《undefined与null的区别》 就是:
null 表示“没有对象”,即该处不应该有值。
所以可以理解为 Object.prototype 没有原型。
所以顺着原型的原型查找的时候,到 Object.prototype 就可以停止了
蓝色的这条线,其实就是大家老生常谈的原型链
tips
真的是继承吗?
最后是关于继承,前面我们讲到“每一个对象都会从原型‘继承’属性”,实际上,继承是一个十分具有迷惑性的说法,引用《你不知道的JavaScript》中的话,就是:
继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。
构造函数的 prototype 设置为 null 会怎样?
1 | function Person() { |
首先new的时候 产生ECMA原生对象,接着此对象的[[Prototype]] 属性是根据 函数PERSTON的prototype来设定的,如果此时prototype是一个对象,[[Prototype]] =那个设定的对象,如果不是一个对象,设定 obj 的 [[Prototype]] 内部属性为 Object 的 prototype 对象
这个问题至今我没有找到合理的答案,上述是引自偶然看到的一个答案,仔细一想也是有道理的,实例是必然会有原型的,首先要明白null
是为空的意思,即 Person.prototype 没有原型,那么没有原型我们怎么办呢,就给他设置为 Object.prototype 吧!感觉这个理解也说得过去,只是迟迟没有在 ECMA 或者某些权威的地方找到论证。
究极理解
这张经典的图,在很多教程上都出现过,理解了它,对于原型,原型链也就差不多了,
- 万物皆对象
- 函数都是有 Function() 创造的
- Function.[[proto]] === Function.prototype 这件事,以后需要专门再一篇总结慢慢道来